package com.yixing.tech.wms.service.impl;

import com.yixing.tech.common.base.BusinessException;
import com.yixing.tech.common.service.AbstractWmsService;
import com.yixing.tech.wms.service.StockLocationService;
import com.yixing.tech.wms.stock.out.ProdPickService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

import static com.yixing.tech.common.CommonConstant.*;
import static com.yixing.tech.common.CommonConstant.InStatus.inStockOK;
import static com.yixing.tech.common.CommonConstant.InventoryStatus.out;

/**
 * 库位服务
 *
 * @author yixing tech
 * @since 1.0.0
 */
@Slf4j
@Service
public class StockLocationServiceImpl extends AbstractWmsService implements StockLocationService {

//    /**
//     * 查询空闲库位
//     */
//    @Override
//    public List<Document> queryEmptyLocation(Collection<String> areaCodes) throws BusinessException {
//        if (CollectionUtils.isEmpty(areaCodes)) {
//            return Collections.emptyList();
//        }
//        Criteria match = newCriteria().and("area").in(areaCodes)
//                .and("lock").is(false)
//                .and("containerCode").isNull();
//        List<AggregationOperation> operations = new LinkedList<>();
//        operations.add(Aggregation.match(match));
//        // 库位优先排序：有库位组按照深度倒排、无库位组
//        // 列值从大到小（列值越大距离拣货区域越近）
//        // 深度从打到小
//        operations.add(Aggregation.sort(Sort.Direction.DESC, "y", "deep"));
//        operations.add(Aggregation.lookup("entity_" + STOCK_AREA, "area", "code", "areaObj"));
//        operations.add(Aggregation.unwind("areaObj", true));
//        return aggregateResult(operations, "entity_" + STOCK_LOCATION);
//    }

    @Resource
    ProdPickService prodPickService;

    /**
     *
     * @param isProd    是否成品: 原材料分配优先分配存放在列值大的库位，成品优先分配存放在列值小的库位
     */
    @Override
    public List<Document> queryEmptyLocation(Collection<String> areaCodes, boolean isProd) throws BusinessException {
        if (CollectionUtils.isEmpty(areaCodes)) {
            return Collections.emptyList();
        }
        Criteria match = newCriteria().and("area").in(areaCodes)
                .and("lock").is(false);

        List<Criteria> orCriteriaList = new LinkedList<>();
        orCriteriaList.add(Criteria.where("containerCode").exists(false));
        orCriteriaList.add(Criteria.where("containerCode").isNull());
        orCriteriaList.add(Criteria.where("containerCode").isNullValue());
        orCriteriaList.add(Criteria.where("containerCode").is(""));
        Criteria subCriteria = new Criteria();
        subCriteria.orOperator(orCriteriaList);
        match.andOperator(subCriteria);

        List<AggregationOperation> operations = new LinkedList<>();
        operations.add(Aggregation.match(match));
        // 库位优先排序：有库位组按照深度倒排、无库位组
        // 列值从大到小（列值越大距离拣货区域越近）
        // 深度从打到小
        if(!isProd) {
            operations.add(Aggregation.sort(Sort.Direction.DESC, "y", "deep"));
        } else {
            operations.add(Aggregation.sort(Sort.Direction.ASC, "y"));
            operations.add(Aggregation.sort(Sort.Direction.DESC, "deep"));
        }
        operations.add(Aggregation.lookup("entity_" + STOCK_AREA, "area", "code", "areaObj"));
        operations.add(Aggregation.unwind("areaObj", true));
        return aggregateResult(operations, "entity_" + STOCK_LOCATION);
    }

    @Override
    public List<Document> pickDasbhoard(String areaCode) {
        if (StringUtils.isBlank(areaCode)) {
            return Collections.emptyList();
        }
        List<Document> locations = simpleFind("entity_" + STOCK_LOCATION, "area", areaCode);
        if (CollectionUtils.isEmpty(locations)) {
            return Collections.emptyList();
        }

        List<String> containerCodes = locations.stream().map(x -> x.getString("containerCode"))
                .collect(Collectors.toList());

        // 查询容器
        List<Document> containers = mongoTemplate.find(Query.query(newCriteria().and("code").in(containerCodes)), Document.class, "entity_" + CONTAINER);
        Map<String, Document> containerTypeMap = new HashMap<>();
        containers.forEach(x -> containerTypeMap.put(x.getString("code"), x.get("type", Document.class)));

        // 采购入库、其他入库
        Map<String, List<Document>> inStockMap = listInStock(containerCodes);
        locations.forEach(x -> {
            String containerCode = x.getString("containerCode");
            if(StringUtils.isNoneBlank(containerCode) && inStockMap.containsKey(containerCode)) {
                containerCodes.remove(containerCode);
                x.put("infoList", inStockMap.get(containerCode));
            }
        });

        // 领料出库、其他出库
        Map<String, List<Document>> outStockMap = listStockOut(containerCodes);
        locations.forEach(x -> {
            String containerCode = x.getString("containerCode");
            if(StringUtils.isNoneBlank(containerCode) && outStockMap.containsKey(containerCode)) {
                containerCodes.remove(containerCode);
                x.put("infoList", outStockMap.get(containerCode));
            }
        });


        // 盘点
        Map<String, List<Document>> inventoryMap = listInventory(containerCodes);
        locations.forEach(x -> {
            String containerCode = x.getString("containerCode");
            if(StringUtils.isNoneBlank(containerCode) && inventoryMap.containsKey(containerCode)) {
                containerCodes.remove(containerCode);
                x.put("infoList", inventoryMap.get(containerCode));
            }
        });

        // 数据整理，填充状态和容器类型
        locations.forEach(x -> {
            boolean lock = x.getBoolean("lock");
            if(lock) {
                x.put("status", "锁定");
            } else {
                String containerCode = x.getString("containerCode");
                if(StringUtils.isNoneBlank(containerCode)) {
                    x.put("containerType", containerTypeMap.get(containerCode));
                }
                List<Document> infoList = x.getList("infoList", Document.class);
                if(StringUtils.isBlank(containerCode)) {
                    x.put("status", "空闲");
                } else if (CollectionUtils.isEmpty(infoList)) {
                    x.put("status", "料箱占位");
                } else {
                    x.put("status", "作业");
                }
            }
        });

        return locations;
    }

    private Map<String, List<Document>> listInventory(Collection<String> containerCodes) {
        if (CollectionUtils.isEmpty(containerCodes)) {
            return Collections.emptyMap();
        }

        List<Document> works = mongoTemplate.find(
                Query.query(newCriteria().and("containerCode").in(containerCodes)
                        .and("status.id").in(new ObjectId(out.getCode()))),
                Document.class, "entity_" + INVENTORY_WORK);
        Map<String, List<Document>> workMapList = new HashMap<>();
        works.forEach(x -> {
            Document doc = new Document();
            doc.put("type", "盘点");
            doc.put("materialCode", x.get("materialCode"));
            doc.put("materialName", x.get("materialName"));
            doc.put("colorName", x.get("colorName"));
            doc.put("languageName", x.get("languageName"));
            doc.put("batchNo", x.get("batchNo"));
            doc.put("checkNum", x.get("checkNum"));
            doc.put("inventoryCode", x.get("inventoryCode"));
            doc.put("partitionSide", x.get("partitionSide"));
            workMapList.computeIfAbsent(x.getString("containerCode"), k -> new ArrayList<>()).add(doc);
        });
        return workMapList;
    }

    private Map<String, List<Document>> listInStock(Collection<String> containerCodes) {
        if (CollectionUtils.isEmpty(containerCodes)) {
            return Collections.emptyMap();
        }

        List<Document> works = mongoTemplate.find(
                Query.query(newCriteria().and("containerCode").in(containerCodes)
                        .and("inStatus.id").ne(new ObjectId(inStockOK.getCode()))),
                Document.class, "entity_" + IN_STOCK_TASK);
        Map<String, List<Document>> workMapList = new HashMap<>();
        works.forEach(x -> {
            Document doc = new Document();
            doc.put("type", "入库");
            doc.put("materialCode", x.get("materialCode"));
            doc.put("materialName", x.get("materialName"));
            doc.put("colorName", x.get("colorName"));
            doc.put("languageName", x.get("languageName"));
            doc.put("batchNo", x.get("batchNo"));
            doc.put("planNum", x.get("planNum"));
            doc.put("matchNum", x.get("matchNum"));
            doc.put("inOrder", x.get("inOrder"));
            doc.put("partitionSide", x.get("partitionSide"));
            workMapList.computeIfAbsent(x.getString("containerCode"), k -> new ArrayList<>()).add(doc);
        });
        return workMapList;
    }

    private Map<String, List<Document>> listStockOut(Collection<String> containerCodes) {
        if (CollectionUtils.isEmpty(containerCodes)) {
            return Collections.emptyMap();
        }

        List<String> podCodeList = containerCodes.stream().filter(ObjectUtils::isNotEmpty).collect(Collectors.toList());
        if(ObjectUtils.isEmpty(podCodeList))
            return Collections.emptyMap();

        List<Document> works = mongoTemplate.find(
                Query.query(newCriteria().and("containerCode").in(podCodeList)
                        .and("inStatus.id").ne(new ObjectId(inStockOK.getCode()))),
                Document.class, "entity_" + IN_STOCK_TASK);

        try {
            List<Map> operationMap = prodPickService.stockOutInfoQueryByPodCode(podCodeList);
            Map<String, List<Document>> workMapList = new HashMap<>();
            operationMap.forEach(x -> {
                Document doc = new Document();
                doc.put("type", "出库");
                doc.put("materialCode", x.get("matCode"));
                doc.put("materialName", x.get("matName"));
                doc.put("colorName", x.get("colorName"));
                doc.put("languageName", x.get("languageName"));
                doc.put("batchNo", x.get("batchNo"));
                doc.put("planNum", x.get("suggestQty"));
                doc.put("matchNum", x.get("matchNum"));
                doc.put("orderNo", x.get("orderNo"));               // TODO
                doc.put("partitionSide", x.get("partitionSide"));
                doc.put("prodMatName", x.get("prodMatName"));       // TODO
                doc.put("prodMatCode", x.get("prodMatCode"));       // TODO
                workMapList.computeIfAbsent(x.get("podCode").toString(), k -> new ArrayList<>()).add(doc);
            });
            return workMapList;
        }catch (Exception ex) {
            log.error("查询原材料出库信息抛出异常:{}", ex);
        }
        return Collections.emptyMap();
    }
}
